/* webserv.c - 一个最小化的 web 服务器 (版本 0.2)
 * 使用方法: ws portnumber
 * 功能: 仅支持 GET 命令
 *       在当前目录运行
 *       为每个请求创建一个新子进程处理
 *       存在重大安全漏洞，仅供演示用途
 *       还有许多其他缺陷，但这是一个良好的开始
 * 编译: cc webserv.c socklib.c -o webserv
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>  // 包含 socket 相关函数的头文件
#include <netinet/in.h>  // 包含 sockaddr_in 的定义
#include <arpa/inet.h>   // 包含 inet_ntoa 定义
#include <netdb.h>       // 包含 gethostbyname 函数的定义

int make_server_socket(int portnum);
void process_rq(char *rq, int fd);
void read_til_crnl(FILE *fp);
void cannot_do(int fd);
void do_404(char *item, int fd);
int isadir(char *f);
int not_exist(char *f);
void do_ls(char *dir, int fd);
char *file_type(char *f);
int ends_in_cgi(char *f);
void do_exec(char *prog, int fd);
void do_cat(char *f, int fd);
void header(FILE *fp, char *content_type);

int main(int ac, char *av[]) {
    int sock, fd;
    FILE *fpin;
    char request[BUFSIZ];

    // 检查命令行参数
    if (ac == 1) {
        fprintf(stderr, "使用方法: ws portnum ");
        exit(1);
    }

    // 创建服务器套接字
    sock = make_server_socket(atoi(av[1]));
    if (sock == -1) exit(2);

    // 主循环
    while (1) {
        // 接受连接并缓冲
        fd = accept(sock, NULL, NULL); // 确保 accept 声明已经包含
        if (fd < 0) {
            perror("接受连接失败");
            continue; // 继续循环以处理下一个连接
        }
        
        fpin = fdopen(fd, "r");

        // 读取请求
        fgets(request, BUFSIZ, fpin);
        printf("接收到请求: %s", request);
        read_til_crnl(fpin);

        // 处理客户端请求
        process_rq(request, fd);

        fclose(fpin);
    }
}

/* ------------------------------------------------------ *
   read_til_crnl(FILE *)
   跳过所有请求信息，直到看到 CRNL
   ------------------------------------------------------ */
void read_til_crnl(FILE *fp) {
    char buf[BUFSIZ];
    while (fgets(buf, BUFSIZ, fp) != NULL && strcmp(buf, "\r ") != 0);
}

/* ------------------------------------------------------ *
   process_rq(char *rq, int fd)
   根据请求执行相应操作并将回复写入 fd
   在新进程中处理请求
   rq 是 HTTP 命令:  GET /foo/bar.html HTTP/1.0
   ------------------------------------------------------ */
void process_rq(char *rq, int fd) {
    char cmd[BUFSIZ], arg[BUFSIZ];

    // 创建新进程，非子进程则返回
    if (fork() != 0) return;

    strcpy(arg, "./"); // 参数前加上 ./
    if (sscanf(rq, "%s %s", cmd, arg + 2) != 2) return;

    if (strcmp(cmd, "GET") != 0) {
        cannot_do(fd);
    } else if (not_exist(arg)) {
        do_404(arg, fd);
    } else if (isadir(arg)) {
        do_ls(arg, fd);
    } else if (ends_in_cgi(arg)) {
        do_exec(arg, fd);
    } else {
        do_cat(arg, fd);
    }
}

/* ------------------------------------------------------ *
   发送回复头: 所有函数都需要一个
   如果 content_type 为 NULL，则不发送内容类型
   ------------------------------------------------------ */
void header(FILE *fp, char *content_type) {
    fprintf(fp, "HTTP/1.0 200 OK\r ");
    if (content_type) {
        fprintf(fp, "Content-type: %s\r ", content_type);
    }
}

/* ------------------------------------------------------ *
   简单函数:
        cannot_do(fd)       未实现的 HTTP 命令
        do_404(item, fd)    没有该对象
   ------------------------------------------------------ */
void cannot_do(int fd) {
    FILE *fp = fdopen(fd, "w");

    fprintf(fp, "HTTP/1.0 501 Not Implemented\r ");
    fprintf(fp, "Content-type: text/plain\r ");
    fprintf(fp, "\r ");
    fprintf(fp, "该命令尚未实现\r ");
    fclose(fp);
}

void do_404(char *item, int fd) {
    FILE *fp = fdopen(fd, "w");

    fprintf(fp, "HTTP/1.0 404 Not Found\r ");
    fprintf(fp, "Content-type: text/plain\r ");
    fprintf(fp, "\r ");
    fprintf(fp, "您请求的项目: %s\r 未找到\r ", item);
    fclose(fp);
}

/* ------------------------------------------------------ *
   目录列表部分
   isadir() 使用 stat，not_exist() 使用 stat
   do_ls 运行 ls。它不应该
   ------------------------------------------------------ */
int isadir(char *f) {
    struct stat info;
    return (stat(f, &info) != -1 && S_ISDIR(info.st_mode));
}

int not_exist(char *f) {
    struct stat info;
    return (stat(f, &info) == -1);
}

void do_ls(char *dir, int fd) {
    FILE *fp;

    fp = fdopen(fd, "w");
    header(fp, "text/plain");
    fprintf(fp, "\r ");
    fflush(fp);

    dup2(fd, 1);
    dup2(fd, 2);
    close(fd);
    execlp("ls", "ls", "-l", dir, NULL);
    perror(dir);
    exit(1);
}

/* ------------------------------------------------------ *
   CGI 相关功能。检查扩展名和
   运行程序的函数。
   ------------------------------------------------------ */
char *file_type(char *f) {
    char *cp;
    if ((cp = strrchr(f, '.')) != NULL) {
        return cp + 1;
    }
    return "";
}

int ends_in_cgi(char *f) {
    return (strcmp(file_type(f), "cgi") == 0);
}

void do_exec(char *prog, int fd) {
    FILE *fp;

    fp = fdopen(fd, "w");
    header(fp, NULL);
    fflush(fp);
    dup2(fd, 1);
    dup2(fd, 2);
    close(fd);
    execl(prog, prog, NULL);
    perror(prog);
}

/* ------------------------------------------------------ *
   do_cat(filename, fd)
   在发送头后返回内容
   ------------------------------------------------------ */
void do_cat(char *f, int fd) {
    char *extension = file_type(f);
    char *content = "text/plain";
    FILE *fpsock, *fpfile;
    int c;

    // 根据文件扩展名设置内容类型
    if (strcmp(extension, "html") == 0) {
        content = "text/html";
    } else if (strcmp(extension, "gif") == 0) {
        content = "image/gif";
    } else if (strcmp(extension, "jpg") == 0 || strcmp(extension, "jpeg") == 0) {
        content = "image/jpeg";
    }

    fpsock = fdopen(fd, "w");
    fpfile = fopen(f, "r");
    if (fpsock != NULL && fpfile != NULL) {
        header(fpsock, content);
        fprintf(fpsock, "\r ");
        while ((c = getc(fpfile)) != EOF) {
            putc(c, fpsock);
        }
        fclose(fpfile);
        fclose(fpsock);
    }
    exit(0);
}

